<html>
  <head>
    <title>更新状态中的数组</title>
  </head>
  <body>
    <script>
        // 更新状态中的数组
                /*
                    数组是另外一种可以存储在状态(state)中的 JavaScript 对象,
                    它虽然是可变的，但是却应该视为"不可变的"。
                    同对象一样，当你想要更新存储于状态(state)中的数组时，你需要创建一个新的数组（或复制现有数组）
                    并使用新数组设置状态(state)
                */


                // 一、更新数组而不改变
                          /*
                                在JavaScript中，数组只是另一种对象。
                                就像对象，你应该将React状态下的数组视为"不可变的"（只读）。
                                这意味着你不应该重新分配像 arr[0] = 'bird'这样的数组内的项目，也不应该使用改变数组的方法,例如 push() 和 pop().

                                而是，每次你想要更新数组时，你都需要将一个新的数组传递给你的"状态设置函数" . 
                                为此，你可以通过调用其非修改方法(如： filter() 和 map())从你所在状态的原始数组创建一个新数组。然后你可以将你的状态，通过状态设置函数设置为这个新数组。
                          */

                          /*
                                这里有一个常见数组操作的参考表。在处理React状态中的数组时，你需要避免使用左侧栏中的方法，而优先使用右侧栏中的方法。
                                  
                                                避免(这类方法修改了数组本身)                             更喜欢(这类语法返回新的数组)

                                  添加            push()、unshift()                                      concat()、[...arr]数组展开语法

                                  删除            pop()、shift()、splice()                                filter()、slice()

                                  替换            splice()、arr[i]=.....                                  map()、[...arr]数组展开语法 

                                  排序            reverse()、sort()                                       首先复制数组，然后再排序
                          */

                          // 或者，你可以使用 Immer  ，它允许你使用上面两列中的任何方法。


                          // 1、添加到数组（数组添加新元素）
                                      
                                     // push() 会改变一个你不想改变的数组。
                                            import {useState} from 'react'
                                            
                                            let nextId= 0;
                                            export default function List(){
                                                const [name,setName]=useState('');
                                                const [artists,setArtists] = useState([]);

                                                return(
                                                    <>
                                                        <h1>Inspiring sculptors:</h1>
                                                        <Input 
                                                            value={name}
                                                            onChange={e=>setName(e.target.value)}
                                                            />
                                                        <button onClick={()=>{
                                                              artists.push({
                                                                id:nextId++,
                                                                name:name
                                                              });
                                                        }}>
                                                        Add
                                                        </button>
                                                        <ul>
                                                          {artists.map(artist => (
                                                            <li key={artist.id}>{artist.name}</li> 
                                                          ))}
                                                        </ul>
                                                    </>
                                                );
                                            }

                                        /*
                                            上面的例子中，直接使用push()方法改变数组，是不允许的。
                                            你应该创建一个新数组，其中包含现有项目和新加的项目。
                                            有多种方法可以实现这一点，但最简单是是用 "..."(数组展开语法)：

                                                    setArtists(  // 状态设置函数
                                                        [
                                                            ...artists, // 展开上次渲染状态快照中的数组
                                                            {
                                                              id:nextId++, // 新的艺术家对象
                                                              name:name
                                                            }
                                                        ]
                                                    );
                                        */ 
                                        // 现在这段代码就可以正常工作了
                                                import { useState } from 'react';
                                                let nextID = 0;
                                                export default function List() {
                                                        const [name, setName] = useState('');
                                                        const [artists, setArtists] = useState([]);

                                                        return (
                                                                <>
                                                                  <h1>Inspiring sculptors:</h1>
                                                                  <input
                                                                        value={name}
                                                                        onChange={e => setName(e.target.value)}
                                                                  />
                                                                  <button onClick={() => {
                                                                    setArtists([
                                                                      ...artists,
                                                                      { id: nextID++, name: name }
                                                                    ]);
                                                                  }}>Add</button>
                                                                  <ul>
                                                                    {artists.map(artist => (
                                                                      <li key={artist.id}>{artist.name}</li>
                                                                    ))}
                                                                  </ul>
                                                                </>
                                                        );
                                                }
                          // 2、从数组中删除元素
                                         /*
                                              从数组中删除元素的最简单方案是将其过滤一下，生成新的数组。请使用filter()方法，该方法会创建一个新数组，而不是改变原来的数组。
                                         */      
                                          
                                         // eg:
                                                import {useState} from 'react';
                                                let initalArtists=[
                                                    { id: 0, name: 'Marta Colvin Andrade' },
                                                    { id: 1, name: 'Lamidi Olonade Fakeye'},
                                                    { id: 2, name: 'Louise Nevelson'},
                                                ]
                                                export default function List2(){
                                                    const [artists,setArtists] = useState(initalArtists);
                                                    
                                                    return(
                                                      <>
                                                        <h1>Inspiring sculptors:</h1>
                                                        <ul>
                                                          {artists.map(artist => (
                                                            <li key={artist.id}>
                                                              {artist.name}{' '}
                                                              <button onClick={() => {
                                                                setArtists(
                                                                  artists.filter(a =>   // 此处使用filter()方法过滤掉数组中的某个元素，生成新数组
                                                                    a.id !== artist.id
                                                                  )
                                                                );
                                                              }}>
                                                                Delete
                                                              </button>
                                                            </li>
                                                          ))}
                                                        </ul>
                                                      </>
                                                    );
                                                }



                          // 3、转换数组
                                     /*
                                          如果要更改数组的部分或全部项目，可以使用 map() 创建新数组。


                                          eg:在此示例中，数组包含两个圆和一个方块的坐标。当你按下按钮时，它只会将圆圈向下移动 50 像素。它通过使用 map() 生成一个新的数据数组来做到这一点：
                                     */
                                                  import { useState } from 'react';
                                                  let initialShapes = [
                                                    { id: 0, type: 'circle', x: 50, y: 100 },
                                                    { id: 1, type: 'square', x: 150, y: 100 },
                                                    { id: 2, type: 'circle', x: 250, y: 100 },
                                                  ];

                                                  export default function ShapeEditor() {
                                                        const [shapes, setShapes] = useState(initialShapes);

                                                        function handleClick() {
                                                            const nextShapes = shapes.map(shape => {
                                                              if (shape.type === 'square') {
                                                                // No change
                                                                return shape;
                                                              } else {
                                                                // Return a new circle 50px below
                                                                return {
                                                                  ...shape,
                                                                  y: shape.y + 50,
                                                                };
                                                              }
                                                            });
                                                            // Re-render with the new array
                                                            setShapes(nextShapes);
                                                        }

                                                        return (
                                                          <>
                                                                <button onClick={handleClick}>
                                                                  Move circles down!
                                                                </button>
                                                                {shapes.map(shape => (
                                                                  <div
                                                                    key={shape.id}
                                                                    style={{
                                                                    background: 'purple',
                                                                    position: 'absolute',
                                                                    left: shape.x,
                                                                    top: shape.y,
                                                                    borderRadius:
                                                                      shape.type === 'circle'
                                                                        ? '50%' : '',
                                                                    width: 20,
                                                                    height: 20,
                                                                  }} />
                                                                ))}
                                                          </>
                                                        );
                                                  }

                        // 4、替换(修改)数组中的条目
                                  /*
                                        想要天魂数组中的一个或多个元素是特别常见的。 
                                        要做到"不改变原始数组的条件下替换数组中的元素"，你也可以使用map()
                                  */
                                          import { useState } from 'react';

                                          let initialCounters = [
                                            0, 0, 0
                                          ];

                                          export default function CounterList() {
                                            const [counters, setCounters] = useState( initialCounters);

                                            function handleIncrementClick(index) {
                                                  const nextCounters = counters.map((c, i) => {
                                                    if (i === index) {
                                                      return c + 1;  // 替换具体元素
                                                    } else {   // 不改变其他元素
                                                      return c;
                                                    }
                                                  });
                                                  setCounters(nextCounters);
                                            }

                                             // 遍历数组，并为每个计数器渲染一个列表项
                                            return (
                                              <ul> 
                                                {counters.map((counter, i) => (
                                                  <li key={i}>
                                                    {counter}
                                                    <button onClick={() => {
                                                      handleIncrementClick(i);
                                                    }}>+1</button>
                                                  </li>
                                                ))}
                                              </ul>
                                            );
                                          }


                      // 5、想新元素插入数组中指定位置
                                  /*
                                      有时候，你可能希望在既不是开头也不是结尾的特定位置插入一个新元素。
                                      为此，你可以将 "..."(数组展开语法) 与 "slice()"一起使用。
                                  */

                                      import { useState } from 'react';
                                      let nextIDD =0;
                                      const initalArtists2=[
                                          { id: 0, name: 'Marta Colvin Andrade' },
                                          { id: 1, name: 'Lamidi Olonade Fakeye'},
                                          { id: 2, name: 'Louise Nevelson'}
                                      ];

                                      export default function List3(){
                                            const [name,setName]= useState('');
                                            const [artists,setArtists]=useState(initalArtists2);

                                            function handleClick(){
                                              const insertAt=1;  // 可以插入任何位置
                                              const nextArtworkists=[
                                                // 插入点之前的元素
                                                ...artists.slice(0,insertAt), // 0 到 insertAt (但不包括insertAt)
                                                { id:nextIDD++,name:name},
                                                ...artists.slice(insertAt) // 从 insertAt 到数组末尾(包含insertAt)
                                              ]
                                            }

                                            return (
                                              <>
                                                <h1>Inspiring sculptors:</h1>
                                                <input
                                                  value={name}
                                                  onChange={e => setName(e.target.value)}
                                                />
                                                <button onClick={handleClick}>
                                                  Insert
                                                </button>
                                                <ul>
                                                  {artists.map(artist => (
                                                    <li key={artist.id}>{artist.name}</li>
                                                  ))}
                                                </ul>
                                              </>
                                            );
                                      }

                                         
                      // 6、对数组进行其他修改(比如排序)
                                      /*
                                          有些事情你不能单独使用扩展语法和非修改方法， 如map() 和 filter() .
                                          例如，你打算对数组进行反转，或者排序。 JavaScript 的reverse() 和 sort() 方法会修改原始数组，因此，你不能直接用它们。
                                          但是， "可以先复制，然后再对其进行更改"
                                      */
                                            import { useState } from 'react';

                                            const initialList = [
                                                { id: 0, title: 'Big Bellies' },
                                                { id: 1, title: 'Lunar Landscape' },
                                                { id: 2, title: 'Terracotta Army' },
                                            ];

                                            export default function List() {
                                                const [list, setList] = useState(initialList);

                                                function handleClick() {
                                                  const nextList = [...list]; // 浅拷贝
                                                  nextList.reverse();  // 对新数组进行反转
                                                  setList(nextList);   // 调用"状态设置函数"
                                                }

                                                return (
                                                  <>
                                                    <button onClick={handleClick}>
                                                      Reverse
                                                    </button>
                                                    <ul>
                                                      {list.map(artwork => (
                                                        <li key={artwork.id}>{artwork.title}</li>
                                                      ))}
                                                    </ul>
                                                  </>
                                                );
                                            }
                                      /*
                                            因为"..."（数组扩展语法）只是浅拷贝，所以它不会复制数组中嵌套的数组和对象。
                                            虽然 nextList和 list 是两个不同的数组， 但是 nextList[0]  和 list[0] 却是指向了同一个对象。
                                            因此，通过直接修改 nextList[0].seen你也在直接修改list[0]。 这就造成了状态(state)的突变，会引发问题！
                                            你可以通过"更新嵌套的JavaScript对象" 类似的方式解决此问题----通过复制你想要更改的单个条目而不是改变他们。
                                      */


                    // 7、更新数组中嵌套的对象
                                     /*
                                            对象并不是真正在数组"里面"。它们在代码中看起来是"在数组里面"，单数组中的每个对象都是单独的"堆栈"，数组中的每个对象元素中都是只储存了指向对象的引用(指针)。
                                            只不过是数值中的元素储存的引用指向了对象，而不是对象(堆栈)本身。
                                            这就是为什么在更新像list[0]这样的嵌套元素时要小心，因为另一个人的代码中的数组的元素可能会指向同一个对象。

                                            更新嵌套状态时，你需要从要更新的点开始创建副本，一直到顶层。
                                     */

                                          import { useState } from 'react';

                                          let nextIddd = 3;
                                          const initialList3 = [
                                            { id: 0, title: 'Big Bellies', seen: false },
                                            { id: 1, title: 'Lunar Landscape', seen: false },
                                            { id: 2, title: 'Terracotta Army', seen: true },
                                          ];

                                          export default function BucketList() {
                                              const [myList, setMyList] = useState(initialList3);
                                              const [yourList, setYourList] = useState(initialList3);

                                              function handleToggleMyList(artworkId, nextSeen) {
                                                const myNextList = [...myList];  // 这样浅拷贝，会造成嵌套对象突变问题
                                                const artwork = myNextList.find(
                                                  a => a.id === artworkId
                                                );
                                                artwork.seen = nextSeen;
                                                setMyList(myNextList);
                                              }

                                              function handleToggleYourList(artworkId, nextSeen) {
                                                const yourNextList = [...yourList];  // 这样浅拷贝，会造成嵌套对象突变问题
                                                const artwork = yourNextList.find(
                                                  a => a.id === artworkId
                                                );
                                                artwork.seen = nextSeen;
                                                setYourList(yourNextList);
                                              }

                                              return (
                                                <>
                                                  <h1>Art Bucket List</h1>
                                                  <h2>My list of art to see:</h2>
                                                  <ItemList
                                                    artworks={myList}
                                                    onToggle={handleToggleMyList} />
                                                  <h2>Your list of art to see:</h2>
                                                  <ItemList
                                                    artworks={yourList}
                                                    onToggle={handleToggleYourList} />
                                                </>
                                              );
                                          }

                                          function ItemList({ artworks, onToggle }) {
                                              return (
                                                <ul>
                                                  {artworks.map(artwork => (
                                                    <li key={artwork.id}>
                                                      <label>
                                                        <input
                                                          type="checkbox"
                                                          checked={artwork.seen}
                                                          onChange={e => {
                                                            onToggle(
                                                              artwork.id,
                                                              e.target.checked
                                                            );
                                                          }}
                                                        />
                                                        {artwork.title}
                                                      </label>
                                                    </li>
                                                  ))}
                                                </ul>
                                              );
                                          }
                                
                                  /*
                                      虽然 myNextList 数组本身是新的，但条目本身与原始 myList 数组中的相同。
                                      因此，更改 artwork.seen 会更改原始艺术品条目。
                                      该艺术品条目也在 yourList 中，这导致了错误。
                                      像这样的错误可能很难考虑，但值得庆幸的是，如果你避免改变状态，它们就会消失。

                                      你可以使用map 将旧海笋替换为更新版本二不会发生突变。

                                            setMyList(myList.map(artwork => {
                                                  if (artwork.id === artworkId) {
                                                      //创建新的对象
                                                    return { ...artwork, seen: nextSeen };
                                                  } else {
                                                      // 不需要改变的对象直接返回即可
                                                    return artwork;
                                                  }
                                            }));


                                      这里是使用创建副本的对象展开语法，使用这种方法，现有状态项都不对发生突变。
                                  */

                // 二、使用Immer编写简洁的更新逻辑
                                  /*
                                        不改变地更新嵌套数组可能会有点重复。就像嵌套对象一样：
                                            1、通常，你不需要更新超过几个层级的状态。如果你的状态对象非常深，你可能需要"以不同的方式重组它们"以便它们扁平。
                                            2、如果你不想更改你的状态结构，你可能更喜欢使用Immer，它允许你使用方便但可变的语法进行编写，并负责为你生成副本。
                                  */

                                       // 下面是使用Immer的示例：】
                                            import {useState}  from 'react';
                                            import {useImmer}  from  'use-immer'

                                            let nextId4 = 3;
                                            const initialList5=[
                                              {id:0,title:'Big Bellies',seen:false},
                                              { id: 1, title: 'Lunar Landscape', seen: false },
                                              { id: 2, title: 'Terracotta Army', seen: true },
                                            ]

                                            export default function BucketList(){
                                                const [myList,updateMyList] = useImmer(initialList5);
                                                const [yourList,updateYourList] =useImmer(initalArtists5);

                                                function handleToggleMyList(id,nextSeen){
                                                   // Immer的draft更新函数，接收一个匿名函数声明作为参数
                                                  updateMyList(draft=>{
                                                      const artwork= draft.find(a=>a.id===id);

                                                      artwork.seen=nextSeen;
                                                  })
                                                }

                                                function handleToggleYourList(artworkId,nextSeen){
                                                  // Immer的"draft更新函数"，接收一个匿名函数声明作(我自己称之为"突变式修改函数")为参数
                                                  updateYourList(draft=>{   // draft(草稿)对象时原始状态的副本，你可以随意修改它，然后Immer会根据草稿生成最终的更新状态。

                                                      const artwork= draft.find(a=>a.id===artworkId);
                                                      artwork.seen=nextSeen;
                                                  })
                                                }

                                                return (
                                                        <>
                                                          <h1>Art Bucket List</h1>
                                                          <h2>My list of art to see:</h2>
                                                          <ItemList
                                                            artworks={myList}
                                                            onToggle={handleToggleMyList} />
                                                          <h2>Your list of art to see:</h2>
                                                          <ItemList
                                                            artworks={yourList}
                                                            onToggle={handleToggleYourList} />
                                                        </>
                                                );
                                            };

                                            function ItemList({ artworks, onToggle }) {
                                                return (
                                                  <ul>
                                                    {artworks.map(artwork => (
                                                      <li key={artwork.id}>
                                                        <label>
                                                          <input
                                                            type="checkbox"
                                                            checked={artwork.seen}
                                                            onChange={e => {
                                                              onToggle(
                                                                artwork.id,
                                                                e.target.checked
                                                              );
                                                            }}
                                                          />
                                                          {artwork.title}
                                                        </label>
                                                      </li>
                                                    ))}
                                                  </ul>
                                                );
                                            }


                                    /*
                                          请注意， 使用Immer时，想 artwork.seen= nextSeen 这样的突变式修改是可以的：

                                                  updateMyTodos(draft => {
                                                          const artwork = draft.find(a => a.id === artworkId);
                                                          artwork.seen = nextSeen;
                                                  });

                                          这是因为你没有改变原始状态，而是改变了Immer提供的 draft(草稿)对象。
                                          你不是在直接的使用"状态设置函数"来更新状态，而是在更改"状态草稿"对象。
                                          在草稿(draft)上你可以进行随意的修改，然后Immer会根据草稿生成最终的状态更新。
                                    */


                            
                                   
                
    </script>
  </body>
</html>